package com.evil.application.strategy.reporting;

import cn.hutool.core.collection.CollectionUtil;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import com.alibaba.fastjson.JSON;
import com.evil.application.enums.reporting.ReportingTypeEnum;
import com.evil.application.enums.reporting.SummaryTypeEnum;
import com.evil.application.pojo.dto.reporting.AggBaseDTO;
import com.evil.application.pojo.dto.reporting.ApplicationReportingDTO;
import com.evil.application.pojo.dto.reporting.ReportingTargetDTO;
import com.evil.application.pojo.dto.reporting.data.ReportingDataRespDTO;
import com.evil.application.pojo.dto.reporting.data.ReportingTableDataDTO;
import com.evil.common.core.util.StreamUtil;
import com.evil.common.es.dto.AggTreeParam;
import com.evil.common.es.dto.AggTreeResult;
import com.evil.common.es.enums.DimensionEnum;
import com.evil.common.es.util.ElasticsearchUtil;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * 报表类型-汇总表
 *
 * @author liyang
 * @date 2022-08-08 00:00
 */
@Slf4j
@Service
@AllArgsConstructor
public class ReportingDataSummaryTableImpl implements ReportingData {

    @Override
    public ReportingTypeEnum type() {
        return ReportingTypeEnum.SUMMARY_TABLE;
    }

    @Override
    public ReportingDataRespDTO calculation(ApplicationReportingDTO reportingDTO, Aggregate aggregations) {
        ReportingDataRespDTO reportingDataRespDTO = new ReportingDataRespDTO(reportingDTO.getReportingKey());
        if (null == aggregations) {
            return reportingDataRespDTO;
        }
        // 聚合树信息
        AggTreeParam<AggBaseDTO> param = this.analysisAggTreeParam(reportingDTO);

        // aggTree
        List<AggTreeResult> aggTree = ElasticsearchUtil.analysis(null, param, aggregations);
        log.info("reportData|summaryTable|key:{}|aggTree:{}", reportingDTO.getReportingKey(), JSON.toJSONString(aggTree));

        if (CollectionUtil.isEmpty(aggTree)) {
            return reportingDataRespDTO;
        }

        // 构建聚合分析
        AnalysisTable analysis = new AnalysisTable(param, new AnalysisTable.Position(0, -1), new AnalysisTable.Position(-1, 0));
        // 首次分析聚合树
        this.analysisAggTree(aggTree, analysis);
        // 初始化表格
        analysis.initTable();
        // 分析计算
        this.analysisCal(aggTree, analysis);

        // 根据 aggTree 与 聚合树信息 生成 二维数据表的表头 List<List<ReportingTableDataDTO>> reportingHeaders
        reportingDataRespDTO.setReportingHeaders(analysis.reportingHeaders());
        // 根据 aggTree 与 聚合树信息 生成 二维数据表 List<List<ReportingTableDataDTO>> collectValues
        reportingDataRespDTO.setCollectValues(analysis.collectValues());

        return reportingDataRespDTO;
    }

    private AnalysisCal analysisCal(List<AggTreeResult> aggTree, AnalysisTable analysisTable) {
        if (CollectionUtil.isEmpty(aggTree)) {
            return new AnalysisCal(1, 1, 1);
        }
        // 记录当前层级共新增多少行
        int addRowNum = 1;
        int addCellNum = 1;
        int addTargetNum = 1;
        for (int i = 0, l = aggTree.size() - 1; i <= l; i++) {
            AggTreeResult a = aggTree.get(i);
            switch (a.getDimensionEnum()) {
                case ROW -> {
                    if (i == 0) {
                        // 开始-行维度列数进1
                        analysisTable.rowPosition.cellNum++;
                    } else {
                        // 非开始-行数进1
                        analysisTable.rowPosition.rowNum++;
                        addRowNum++;
                    }
                    // 列维度重置 -1行0列
                    analysisTable.cellPosition.reset(-1, 0);
                    // 行维度-补全
                    ArrayList<String> rowKeys = new ArrayList<>(analysisTable.getRowKeyMap().keySet());
                    // 行维度-补全-获取当前列维度的索引
                    int index = rowKeys.indexOf(a.getTitle());
                    // 行维度-补全-最后一个行维度 补全列维度
                    if (index >= rowKeys.size() - 1) {
                        // 判断是否有列维度，有则补全列维度
                        if (!analysisTable.getCellKeyMap().isEmpty()) {
                            analysisTable.getCellKeyMap().getKey(0).ifPresent(first -> {
                                // 现有key
                                Map<String, AggTreeResult> keyMap = StreamUtil.toMapK(a.getChildren(), e -> String.valueOf(e.getValue()));
                                // 所有的key
                                LinkedHashSet<String> allKeys = analysisTable.getCellKeyMap().get(first);
                                // 补全子级
                                a.completion(allKeys, DimensionEnum.COLUMN, first, keyMap);
                            });
                        }
                        // 判断是否有指标，有则补全指标
                        else if (!analysisTable.getParam().getTargetMap().isEmpty()) {
                            // 现有key
                            Map<String, AggTreeResult> keyMap = StreamUtil.toMapK(a.getChildren(), e -> String.valueOf(e.getTitle()));
                            // 所有的key
                            Set<String> allKeys = analysisTable.getParam().getTargetMap().keySet();
                            // 补全子级
                            a.completion(allKeys, DimensionEnum.TARGET, null, keyMap);
                        }
                    }

                    // 获取当前行
                    AnalysisTable.Row currentRow = analysisTable.rowTable.find(analysisTable.rowPosition.rowNum);
                    // 递归
                    int childrenRowNum = this.analysisCal(a.getChildren(), analysisTable).rowNum;
                    // 当前行分桶信息格子
                    currentRow.find(analysisTable.rowPosition.cellNum).set(1, childrenRowNum, a.getValue(), false);

                    // 当前行分桶需要隐藏的合并格子（子级有多个，则需要新增对应的行，并且标识为隐藏单元格）
                    if (childrenRowNum > 1) {
                        for (int n = 0; n < childrenRowNum - 1; n++) {
                            analysisTable.rowTable.find(analysisTable.rowPosition.rowNum - n).find(analysisTable.rowPosition.cellNum).set(true);
                        }
                        // 需要额外新增 多子级 多出来的行
                        addRowNum += (childrenRowNum - 1);
                    }
                    if (i == l) {
                        // 结束-列数减1
                        analysisTable.rowPosition.cellNum--;
                    }
                }
                case COLUMN -> {
                    if (i == 0) {
                        // 开始-行数进1
                        analysisTable.cellPosition.rowNum++;
                    } else {
                        // 非开始-列数进1
                        analysisTable.cellPosition.cellNum++;
                        addCellNum++;
                    }
                    // 列维度-补全
                    ArrayList<String> cellKeys = new ArrayList<>(analysisTable.getCellKeyMap().keySet());
                    // 列维度-补全-获取当前列维度的索引
                    int index = cellKeys.indexOf(a.getTitle());
                    // 列维度-补全-非最后一个列维度 补全列维度
                    if (index < cellKeys.size() - 1) {
                        // 现有key
                        Map<String, AggTreeResult> keyMap = StreamUtil.toMapK(a.getChildren(), e -> String.valueOf(e.getValue()));
                        String cellKey = cellKeys.get(index + 1);
                        // 所有的key
                        LinkedHashSet<String> allKeys = analysisTable.getCellKeyMap().get(cellKey);
                        // 补全子级
                        a.completion(allKeys, DimensionEnum.COLUMN, cellKey, keyMap);
                    }
                    // 列维度-补全-最后一个列维度 补全指标
                    else if (!analysisTable.getParam().getTargetMap().isEmpty()) {
                        // 现有key
                        Map<String, AggTreeResult> keyMap = StreamUtil.toMapK(a.getChildren(), e -> String.valueOf(e.getTitle()));
                        // 所有的key
                        Set<String> allKeys = analysisTable.getParam().getTargetMap().keySet();
                        // 补全子级
                        a.completion(allKeys, DimensionEnum.TARGET, null, keyMap);
                    }

                    // 获取当前行
                    AnalysisTable.Row currentRow = analysisTable.cellTable.find(analysisTable.cellPosition.rowNum);
                    // 分桶信息格子-先获取
                    ReportingTableDataDTO td = currentRow.find(analysisTable.cellPosition.cellNum);
                    // 递归
                    int childrenCellNum = this.analysisCal(a.getChildren(), analysisTable).cellNum;
                    // 分桶信息格子-再设置（否则递归后可能有问题）
                    td.set(childrenCellNum, 1, a.getValue(), false);

                    // 当前列分桶需要隐藏的合并格子（子级有多个，则需要新增对应的列，并且是合并单元格）
                    if (childrenCellNum > 1) {
                        for (int n = 0; n < childrenCellNum - 1; n++) {
                            currentRow.find(analysisTable.cellPosition.cellNum - n).set(true);
                        }
                        // 需要额外新增 多子级 多出来的列
                        addCellNum += (childrenCellNum - 1);
                    }
                    if (i == l) {
                        // 结束-行数减1
                        analysisTable.cellPosition.rowNum--;
                    }
                }
                case TARGET ->
                        analysisTable.findTargetTableRow(a.getTitle(), analysisTable.rowPosition.rowNum).find(analysisTable.cellPosition.cellNum)
                                .set(1, 1, a.getValue(), false);
            }
        }
        return new AnalysisCal(addRowNum, addCellNum, addTargetNum);
    }

    private static class AnalysisTable extends ReportingData.Analysis {
        private final Position rowPosition;
        private final Position cellPosition;

        private Table rowTable;
        private Table cellTable;
        private Map<String, Table> targetTableMap;

        public AnalysisTable(AggTreeParam<AggBaseDTO> param, Position rowPosition, Position cellPosition) {
            super(param);

            this.rowPosition = rowPosition;
            this.cellPosition = cellPosition;
        }

        public void initTable() {
            if (this.getRowKeyMap().isEmpty()) {
                this.rowTable = new Table(0, -1); // 1行无列
            } else {
                this.rowTable = new Table(this.getRowNum(), this.getRowKeyMap().size() - 1);
            }
            if (this.getCellKeyMap().isEmpty()) {
                this.cellTable = new Table(0, 0); // 1行1列
            } else {
                int c = this.getCellKeyMap().values().stream().map(HashSet::size).reduce(1, (a, b) -> a * b) - 1;
                this.cellTable = new Table(this.getCellKeyMap().size() - 1, c);
            }
            this.targetTableMap = new LinkedHashMap<>();
        }

        public Row findTargetTableRow(String title, int rowNum) {
            if (!targetTableMap.containsKey(title)) {
                int c = this.getCellKeyMap().values().stream().map(HashSet::size).reduce(1, (a, b) -> a * b) - 1;
                targetTableMap.put(title, new Table(this.getRowNum(), c));
            }
            return targetTableMap.get(title).find(rowNum);
        }

        public List<List<ReportingTableDataDTO>> reportingHeaders() {
            List<List<ReportingTableDataDTO>> reportingHeaders = new ArrayList<>();
            ArrayList<? extends Map.Entry<String, ? extends AggBaseDTO>> rowEntries = new ArrayList<>(this.getParam().getRowMap().entrySet());
            ArrayList<? extends Map.Entry<String, ? extends AggBaseDTO>> cellEntries = new ArrayList<>(this.getParam().getColumnMap().entrySet());

            List<ReportingTableDataDTO> firstRow = new ArrayList<>();
            for (int ri = 0, rl = rowEntries.size() - 1; ri <= rl; ri++) {
                if (ri == rl) {
                    firstRow.add(new ReportingTableDataDTO("指标"));
                } else {
                    firstRow.add(new ReportingTableDataDTO());
                }
            }
            // 遍历指标--构建复制各个指标的表头
            List<Table> tables = new ArrayList<>();
            this.getParam().getTargetMap().forEach((tk, tv) -> {
                Table copy = cellTable.copy();
                if (tv instanceof ReportingTargetDTO target) {
                    // 需要小计 && 有列维度
                    if (target.needTotal() && !cellEntries.isEmpty()) {
                        for (int i = 0, l = copy.size() - 1; i <= l; i++) {
                            Row r = copy.get(i);
                            if (i == l) {
                                r.add(new ReportingTableDataDTO(1, 1, String.format("小计(%s)", SummaryTypeEnum.findNameById(target.getTotalType(), "")), true));
                            } else {
                                r.add(new ReportingTableDataDTO(true, true));
                            }
                        }
                    }
                }
                tables.add(copy);

                int cellNum = copy.get(0).size();
                firstRow.add(new ReportingTableDataDTO(cellNum, 1, tv.getTitle(), false));
                for (int i = 0; i < cellNum - 1; i++) {
                    firstRow.add(new ReportingTableDataDTO(true));
                }
            });
            reportingHeaders.add(firstRow);

            // 遍历列维度（列维度控制表头有多少行）
            for (int ci = 0, cl = cellEntries.size() - 1; ci <= cl; ci++) {
                // 新增一行
                List<ReportingTableDataDTO> row = new ArrayList<>();
                // 遍历行维度（行维度控制左边增加多少列）
                for (int ri = 0, rl = rowEntries.size() - 1; ri <= rl; ri++) {
                    // 新增格子
                    ReportingTableDataDTO td = new ReportingTableDataDTO(true);
                    // 格子标题
                    String title = "";
                    // 格子标题--最后一行 每一列显示行维度标题
                    if (ci == cl) {
                        title += rowEntries.get(ri).getKey();
                    }
                    // 格子标题--最后一列 每一行显示列维度标题
                    if (ri == rl) {
                        title += ((StringUtils.isBlank(title) ? "" : "/") + cellEntries.get(ci).getKey());
                    }

                    td.set(1, 1, title, false);
                    row.add(td);
                }

                // 把各个指标的表头 复制到 总表头
                for (Table table : tables) {
                    row.addAll(table.get(ci));
                }
                reportingHeaders.add(row);
            }
            return reportingHeaders;
        }

        public List<List<ReportingTableDataDTO>> collectValues() {
            List<List<ReportingTableDataDTO>> collectValues = rowTable.copy().toList();

            // 标记是否任一指标需要列汇总
            AtomicBoolean needColumnSummary = new AtomicBoolean(false);
            LinkedHashMap<String, ReportingTargetDTO> targetMap = new LinkedHashMap<>();
            this.getParam().getTargetMap().forEach((tk, tv) -> {
                if (!(tv instanceof ReportingTargetDTO target)) {
                    return;
                }
                if (!this.targetTableMap.containsKey(tk)) {
                    return;
                }
                if (target.needSummary()) needColumnSummary.set(true);
                targetMap.put(tk, target);
            });
            AtomicBoolean added = new AtomicBoolean(false);
            targetMap.forEach((tk, target) -> {
                // #获取当前指标 数据表
                Table table = this.targetTableMap.get(tk);
                // #小计 (需要小计 && 有列维度)
                if (target.needTotal() && !this.getParam().getColumnMap().isEmpty()) {
                    Optional<SummaryTypeEnum> totalSummaryOptional = SummaryTypeEnum.isExistById(target.getTotalType());
                    totalSummaryOptional.ifPresent(totalSummary -> {
                        for (int i = 0, l = table.size() - 1; i <= l; i++) {
                            Row r = table.get(i);
                            // 小计列
                            r.add(new ReportingTableDataDTO(1, 1, totalSummary.getSummary().apply(r).orElse(""), true));
                        }
                    });
                }

                // #列汇总 (需要列汇总 && 有行维度)
                if (target.needSummary() && !this.getParam().getRowMap().isEmpty()) {
                    Optional<SummaryTypeEnum> columnSummaryOptional = SummaryTypeEnum.isExistById(target.getColumnSummaryType());
                    // 如果其他指标有列汇总，那么就算当前指标没有列汇总，也得添加一个空行
                    if (needColumnSummary.get()) {
                        columnSummaryOptional = columnSummaryOptional.or(() -> Optional.of(SummaryTypeEnum.NO));
                    }
                    columnSummaryOptional.ifPresent(columnSummary -> {
                        Table inverse = table.inverse(); // 反转表格，行变列 列变行
                        Row summary = new Row();
                        for (int i = 0, l = inverse.size() - 1; i <= l; i++) {
                            Row r = inverse.get(i);
                            // 列汇总
                            summary.add(new ReportingTableDataDTO(1, 1, columnSummary.getSummary().apply(r).orElse(""), true));
                        }
                        // 列汇总行 添加到表格
                        table.add(summary);
                    });
                    // 添加列汇总行
                    if (needColumnSummary.get() && !added.get()) {
                        List<ReportingTableDataDTO> summaryRow = new ArrayList<>();
                        for (int i = 0, l = this.getParam().getRowMap().size() - 1; i < l; i++) {
                            summaryRow.add(new ReportingTableDataDTO(false));
                        }
                        summaryRow.add(new ReportingTableDataDTO(1, 1, String.format("列汇总(%s)", SummaryTypeEnum.findNameById(target.getColumnSummaryType(), "")), false));
                        collectValues.add(summaryRow);
                        added.set(true);
                    }
                }

                // #格式化
                if (target.needFormat()) {
                    for (int i = 0, l = table.size() - 1; i <= l; i++) {
                        Row r = table.get(i);
                        for (int j = 0, ll = r.size() - 1; j <= ll; j++) {
                            ReportingTableDataDTO td = r.get(j);
                            td.set(target.format(td.getV()));
                        }
                    }
                }

                // #指标数据表 添加到汇总表
                for (int i = 0, l = table.size() - 1; i <= l; i++) {
                    if (collectValues.size() > i) {
                        collectValues.get(i).addAll(table.get(i));
                    } else {
                        collectValues.add(table.get(i));
                    }
                }
            });

            return collectValues;
        }

        @NoArgsConstructor
        @AllArgsConstructor
        private static class Position {
            private int rowNum = -1;
            private int cellNum = -1;

            public void reset(int rowNum, int cellNum) {
                this.rowNum = rowNum;
                this.cellNum = cellNum;
            }
        }

        @NoArgsConstructor
        private static class Table extends ArrayList<Row> {

            public Table(int rowNum, int cellNum) {
                super();
                for (int i = 0; i <= rowNum; i++) {
                    this.add(new Row(cellNum));
                }
            }

            public Row find(int rowNum) {
                if (this.size() - 1 < rowNum) {
                    for (int i = 0, lackRow = rowNum - this.size(); i <= lackRow; i++) {
                        this.add(new Row());
                    }
                }
                return this.get(rowNum);
            }

            public Table copy() {
                Table newTable = new Table();
                for (Row row : this) {
                    // 假设 ReportingTableDataDTO 提供了 clone 方法
                    newTable.add(row.copy());
                }
                return newTable;
            }

            public Table inverse() {
                Table inverse = new Table();

                if (this.isEmpty()) {
                    return inverse;
                }

                int rowCount = this.size();
                int colCount = this.get(0).size();

                for (int i = 0; i < colCount; i++) {
                    Row newRow = new Row();
                    for (int j = 0; j < rowCount; j++) {
                        newRow.add(this.get(j).get(i));
                    }
                    inverse.add(newRow);
                }

                return inverse;
            }

            public List<List<ReportingTableDataDTO>> toList() {
                return this.stream().map(Row::toList).collect(Collectors.toList());
            }
        }

        @NoArgsConstructor
        private static class Row extends ArrayList<ReportingTableDataDTO> {

            public Row(int cellNum) {
                super();
                for (int i = 0; i <= cellNum; i++) {
                    this.add(new ReportingTableDataDTO());
                }
            }

            public ReportingTableDataDTO find(int cellNum) {
                if (this.size() - 1 < cellNum) {
                    for (int i = 0, lackRow = cellNum - this.size(); i <= lackRow; i++) {
                        this.add(new ReportingTableDataDTO());
                    }
                }
                return this.get(cellNum);
            }

            public Row copy() {
                Row newRow = new Row();
                for (ReportingTableDataDTO dataDTO : this) {
                    // 假设 ReportingTableDataDTO 提供了 clone 方法
                    newRow.add(dataDTO.copy());
                }
                return newRow;
            }

            public List<ReportingTableDataDTO> toList() {
                return this;
            }
        }
    }

    private record AnalysisCal(int rowNum, int cellNum, int targetNum) {
    }
}
